/*
 * Created on 02-Jan-2005
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package org.gudy.azureus2.core3.tracker.server.impl.tcp.blocking;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.tracker.server.TRTrackerServerException;
import org.gudy.azureus2.core3.tracker.server.impl.tcp.TRTrackerServerProcessorTCP;
import org.gudy.azureus2.core3.tracker.server.impl.tcp.TRTrackerServerTCP;
import org.gudy.azureus2.core3.util.AETemporaryFileHandler;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;

/**
 * @author parg
 * 
 */

public class TRBlockingServerProcessor extends TRTrackerServerProcessorTCP {
    protected static final int KEEP_ALIVE_SOCKET_TIMEOUT = 30 * 1000;

    private static final LogIDs LOGID = LogIDs.TRACKER;
    protected Socket socket;

    protected int timeout_ticks = 1;
    protected String current_request;

    protected TRBlockingServerProcessor(TRTrackerServerTCP _server, Socket _socket) {
        super(_server);

        socket = _socket;
    }

    public void runSupport() {
        // System.out.println( "Processor starts: " + socket.getRemoteSocketAddress());

        boolean keep_alive = getServer().isKeepAliveEnabled();

        try {
            InputStream is = new BufferedInputStream(socket.getInputStream());

            while (true) {

                setTaskState("entry");

                try {
                    if (keep_alive) {

                        socket.setSoTimeout(KEEP_ALIVE_SOCKET_TIMEOUT);

                        setTimeoutsDisabled(true);

                    } else {

                        socket.setSoTimeout(SOCKET_TIMEOUT);
                    }
                } catch (Throwable e) {

                    // e.printStackTrace();
                }

                setTaskState("reading header");

                try {

                    byte[] buffer = new byte[16 * 1024];
                    int header_pos = 0;

                    while (header_pos < buffer.length) {

                        int len = is.read(buffer, header_pos, 1);

                        if (len != 1) {

                            throw (new Exception("Premature end of stream reading header"));
                        }

                        header_pos++;

                        if (header_pos >= 4 && buffer[header_pos - 4] == CR && buffer[header_pos - 3] == FF && buffer[header_pos - 2] == CR
                                && buffer[header_pos - 1] == FF) {

                            break;
                        }
                    }

                    String header = new String(buffer, 0, header_pos, Constants.BYTE_ENCODING);

                    if (Logger.isEnabled()) {

                        String log_str = header;

                        int pos = log_str.indexOf(NL);

                        if (pos != -1) {

                            log_str = log_str.substring(0, pos);
                        }

                        Logger.log(new LogEvent(LOGID, "Tracker Server: received header '" + log_str + "' from " + socket.getRemoteSocketAddress()));
                    }

                    // System.out.println( "got header:" + header );

                    InputStream post_is = null;
                    File post_file = null;

                    String lowercase_header;

                    boolean head = false;

                    int url_start;

                    if (header.startsWith("GET ")) {

                        timeout_ticks = 1;

                        lowercase_header = header.toLowerCase();
                        url_start = 4;

                    } else if (header.startsWith("HEAD ")) {

                        timeout_ticks = 1;

                        lowercase_header = header.toLowerCase();
                        url_start = 5;

                        head = true;

                    } else if (header.startsWith("POST ")) {

                        timeout_ticks = TRTrackerServerTCP.PROCESSING_POST_MULTIPLIER;

                        if (timeout_ticks == 0) {

                            setTimeoutsDisabled(true);
                        }

                        setTaskState("reading content");

                        lowercase_header = header.toLowerCase();
                        url_start = 5;

                        String cl_str = getHeaderField(header, lowercase_header, "content-length:");

                        if (cl_str == null) {

                            if (lowercase_header.contains("transfer-encoding") && lowercase_header.contains("chunked")) {

                                Debug.out("Chunked transfer-encoding not supported!!!!");
                            }

                            cl_str = "0";
                        }

                        int content_length = Integer.parseInt(cl_str);

                        ByteArrayOutputStream baos = null;
                        FileOutputStream fos = null;

                        try {
                            OutputStream data_os;

                            if (content_length <= 256 * 1024) {

                                baos = new ByteArrayOutputStream();

                                data_os = baos;

                            } else {

                                post_file = AETemporaryFileHandler.createTempFile();

                                post_file.deleteOnExit();

                                fos = new FileOutputStream(post_file);

                                data_os = fos;
                            }

                            while (content_length > 0) {

                                int len = is.read(buffer, 0, Math.min(content_length, buffer.length));

                                if (len < 0) {

                                    throw (new TRTrackerServerException("premature end of input stream"));
                                }

                                data_os.write(buffer, 0, len);

                                content_length -= len;
                            }

                            if (baos != null) {

                                post_is = new ByteArrayInputStream(baos.toByteArray());

                            } else {

                                fos.close();

                                fos = null;

                                post_is = new BufferedInputStream(new FileInputStream(post_file), 256 * 1024);
                            }

                            // System.out.println( "TRTrackerServerProcessorTCP: request data = " + baos.size());

                        } finally {
                            // tidy up open streams
                            if (baos != null) {
                                try {
                                    baos.close();
                                } catch (Throwable e) {
                                }
                            }
                            if (fos != null) {
                                try {
                                    fos.close();
                                } catch (Throwable e) {
                                }
                            }
                            // handle failure case - we should always have a post_is here, if not then
                            // we've errored so delete any temp file
                            if (post_is == null && post_file != null) {
                                post_file.delete();
                            }
                        }
                    } else {

                        int pos = header.indexOf(' ');

                        if (pos == -1) {

                            throw (new TRTrackerServerException("header doesn't have space in right place"));
                        }

                        timeout_ticks = 1;

                        lowercase_header = header.toLowerCase();
                        url_start = pos + 1;
                    }

                    setTaskState("processing request");

                    current_request = header;

                    try {
                        if (post_is == null) {

                            // set up a default input stream

                            post_is = new ByteArrayInputStream(new byte[0]);
                        }

                        int url_end = header.indexOf(" ", url_start);

                        if (url_end == -1) {

                            throw (new TRTrackerServerException("header doesn't have space in right place"));
                        }

                        String url = header.substring(url_start, url_end).trim();

                        int nl_pos = header.indexOf(NL, url_end);

                        if (nl_pos == -1) {

                            throw (new TRTrackerServerException("header doesn't have nl in right place"));
                        }

                        String http_ver = header.substring(url_end, nl_pos).trim();

                        String con_str = getHeaderField(header, lowercase_header, "connection:");

                        if (con_str == null) {

                            if (http_ver.equalsIgnoreCase("HTTP/1.0")) {

                                keep_alive = false;
                            }
                        } else if (con_str.equalsIgnoreCase("close")) {

                            keep_alive = false;
                        }

                        if (head) {

                            ByteArrayOutputStream head_response = new ByteArrayOutputStream(4096);

                            if (!processRequest(header, lowercase_header, url, (InetSocketAddress) socket.getLocalSocketAddress(),
                                    (InetSocketAddress) socket.getRemoteSocketAddress(), false, keep_alive, post_is, head_response, null)) {

                                keep_alive = false;
                            }

                            byte[] head_data = head_response.toByteArray();

                            int header_length = head_data.length;

                            for (int i = 3; i < head_data.length; i++) {

                                if (head_data[i - 3] == CR && head_data[i - 2] == FF && head_data[i - 1] == CR && head_data[i] == FF) {

                                    header_length = i + 1;

                                    break;
                                }
                            }

                            setTaskState("writing head response");

                            socket.getOutputStream().write(head_data, 0, header_length);

                            socket.getOutputStream().flush();

                        } else {

                            if (!processRequest(header, lowercase_header, url, (InetSocketAddress) socket.getLocalSocketAddress(),
                                    (InetSocketAddress) socket.getRemoteSocketAddress(), false, keep_alive, post_is, socket.getOutputStream(), null)) {

                                keep_alive = false;
                            }
                        }
                    } finally {

                        if (post_is != null) {

                            post_is.close();
                        }

                        if (post_file != null) {

                            post_file.delete();
                        }
                    }
                } catch (Throwable e) {

                    keep_alive = false;

                    // e.printStackTrace();
                }

                if (!keep_alive) {

                    break;
                }
            }
        } catch (Throwable e) {

        } finally {

            setTaskState("final socket close");

            try {
                socket.close();

            } catch (Throwable e) {

                // e.printStackTrace();
            }

            // System.out.println( "Processor ends: " + socket.getRemoteSocketAddress());
        }
    }

    protected String getHeaderField(String header, String lc_header, String field) {
        int start = lc_header.indexOf(field);

        if (start == -1) {

            return (null);
        }

        int end = header.indexOf(NL, start);

        if (end == -1) {

            return (null);
        }

        return (header.substring(start + field.length(), end).trim());

    }

    public void interruptTask() {
        try {
            if (areTimeoutsDisabled()) {

                // Debug.out( "External tracker request timeout ignored: state = " + getTaskState() + ", req = " + current_request );

            } else {

                timeout_ticks--;

                if (timeout_ticks <= 0) {

                    System.out.println("Tracker task interrupted in state '" + getTaskState() + "' : processing time limit exceeded for "
                            + socket.getInetAddress());

                    socket.close();
                }
            }

        } catch (Throwable e) {

            // e.printStackTrace();
        }
    }

}
